Quick Intro to R Notebooks and R Markdown

This is an R Markdown Notebook. It combines markdown, a plain text formatting syntax, and R code. When you execute R code within the notebook, the output appears beneath the code.

This file was created in RStudio by going to File…New File…R Notebook.

R code needs to be in “chunks” for it to work. Below is an example of an R code chunk. It makes a parabola using base R graphics (not ggplot2).

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter (Win/Linux) or Cmd+Shift+Return (Mac).

x <- seq(-1, 1, by = 0.01)
y <- x^2
plot(x, y, type = "l")

To hide the output, click the Expand/Collapse output button. To clear results (or an error), click the “x”.

You can also press Ctrl+Enter (Win/Linux) or Cmd+Return (Mac) to run one line of code at a time (instead of the entire chunk).

Add a new R code chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I (Win/Linux) or Cmd+Option+I (Mac).

Code along 0

Insert a new R code chunk below and type and run the code: 22/7

Import data

Let’s import our Albemarle County real estate data. We use the readRDS() function. Recall that an rds file is simply any R object that’s been saved. In this case we saved a data frame. Below we read in the rds file via URL. To do this we need to use the base R url() function to open a connection to the website where the file is stored.

URL <- "https://github.com/uvastatlab/DJA/raw/main/data/albemarle_homes_2022.rds"
homes <- readRDS(url(URL))

Let’s look at the first few rows:

head(homes)

Variable name definitions:

Load packages we’ll use today

Intro to ggplot2

The ggplot2 package implements The Grammar of Graphics as defined in the book of the same name by Leland Wilkinson.

It requires your data be in a data frame (or tibble).

Let’s do a quick example. How does the total value of a home relate to its size in finished square feet? Plot TotalValue versus FinSqFt to create a scatter plot.

What just happened?

Don’t forget the parentheses on geom_point()!

Most of what we do with ggplot2 takes this general form

ggplot(data) +
  aes() +
  geom_something()
  

Or this form, with the aesthetics defined in the ggplot() function:

ggplot(data, aes()) +
  geom_something()

There are many geom_ functions in ggplot2. This naming convention makes them easier to find using RStudio’s autocomplete functionality.

It can get much more complicated, but for exploratory visualization this often sufficient.

Code along 1

Do younger (more recently built) houses have a tendency to be bigger than older houses? Create a scatterplot of FinSqFt (y-axis) versus Age (x-axis).

Reminder: Insert a new code chunk by pressing Ctrl+Alt+I (Win/Linux) or Cmd+Option+I (Mac).

ggplot(homes) +
  aes(x = Age, y = FinSqFt) +
  geom_point(alpha = 1/10)

More aesthetics and geoms, and introducing facets

Points not only have positions in a plane, but can be different sizes and have different shapes and colors. We can “map” variables in our data frame to these aesthetics. In other words, we let variables define properties of points such as size, shape, and color.

How does the relationship between TotalValue and FinSqFt differ between homes with and without central air?

Plot TotalValue versus FinSqFt to create a scatter plot, but also color the points according to whether or not the homes have central air (Cooling) using the color argument.

ggplot(homes) +
  aes(x = FinSqFt, y = TotalValue, color = Cooling) +
  geom_point() 

We can also map the size of the points to a variable, such as LotSize, using the size argument. Notice I have also mapped the shape of the point to Cooling using the shape argument.

ggplot(homes) +
  aes(x = FinSqFt, y = TotalValue, 
      shape = Cooling, size = LotSize) +
  geom_point() 

Notice the overplotting. Perhaps we prefer to see separate scatterplots for each Cooling category. We can achieve that with facets. The function to use is facet_wrap() with the variable that we want to facet on. In this case it’s Cooling. Notice we precede the variable with a tilde ~. We can read that as “facet by Cooling”.

ggplot(homes) +
  aes(x = FinSqFt, y = TotalValue, size = LotSize) +
  geom_point(alpha = 1/2) +
  facet_wrap(~ Cooling)

We can also have multiple geoms in a plot. Let’s say we want to add a smooth trend line that summarizes the relationship between FinSqFt and TotalValue. Add the geom_smooth()

ggplot(homes) +
  aes(x = FinSqFt, y = TotalValue) +
  geom_point(alpha = 1/2) +
  geom_smooth() +
  facet_wrap(~ Cooling) 
`geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'

Code along 2

How is TotalValue related to LotSize within each high school district?

Create a scatterplot of TotalValue (y-axis) vs LotSize (x-axis), faceted by HSDistrict. Add smooth trend lines to summarize the relationship.

Reminder: Insert a new code chunk by pressing Ctrl+Alt+I (Win/Linux) or Cmd+Option+I (Mac).

ggplot(homes) +
  aes(x = LotSize, y = TotalValue) +
  geom_point() +
  geom_smooth() +
  facet_wrap(~HSDistrict)
`geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'

Binning and zooming

Dealing with large amounts to data leads to overplotting. So far we’ve used alpha values to modify transparency from 0 (invisible) to 1 (solid), facets, and different geometric shapes to help alleviate this. Another is “binning”. As long as you have the hexbin package installed, this can be easily accomplished with geom_hex. This “bins” counts of points into hexagons and colors the hexagons according to counts. Lighter colors mean higher counts. Changing the number of bins (default 30 x 30) decreases/increases the number of hexagons. For example, bins = 40

# install.packages("hexbin")
ggplot(homes) +
  aes(x = FinSqFt, y = TotalValue) +
  geom_hex(bins = 40) 

Albemarle county has homes with very large TotalValue and LotSize values. Including them in our plots means we “zoom out” to accommodate them which means we’re “far away” from the majority of homes.

To zoom in on data with ggplot2, we use the coord_cartesion() function and define the limits to view with the xlim and/or ylim arguments.

ggplot(homes) +
  aes(x = FinSqFt, y = TotalValue) +
  geom_point() +
  geom_smooth() + 
  coord_cartesian(xlim = c(0,5000), 
                  ylim = c(0, 2e6)) # from 0 to $2,000,000
`geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'

If presenting this plot to an audience, tell them it’s zoomed in and looking at a subset of your data.

The numbers are a little hard to read on the y-axis. We can format them as dollar amounts using the labels argument in the scale_y_continuous() function. To do this we can use the dollar function from the scales package, which is installed when you install the ggplot2 package.

ggplot(homes) +
  aes(x = FinSqFt, y = TotalValue) +
  geom_point() +
  geom_smooth() +
  coord_cartesian(xlim = c(0, 5000),
                  ylim = c(0, 2e6)) + 
  scale_y_continuous(labels = dollar) 

Code along 3

Modify your code from code along 2 to zoom in on the y-axis to see home prices ranging from 0 to $1,000,000, and zoom in on the x-axis to view homes on lots ranging in size from 0 to 5 acres. Also use the dollar function to format the home prices on the y-axis.

ggplot(homes) +
  aes(x = LotSize, y = TotalValue) +
  geom_point() +
  geom_smooth() +
  facet_wrap(~ HSDistrict) +
  coord_cartesian(xlim = c(0, 5),
                  ylim = c(0, 1e6)) +
  scale_y_continuous(labels = dollar)
`geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'

Visualizing counts

Recall the condition variable. We can quickly get counts of each condition using the count() function from the dplyr package:

homes %>% count(Condition)

One way to visualize counts is with a bar plot. We can create a bar plot in ggplot2 with geom_bar(). Notice it automatically generates the counts for us.

One of the aesthetics of bars is their “fill” color. We can map a variable from our data frame to the fill aesthetic. For example, let’s see counts of conditions for homes with and without central air (Cooling).

The default result is to stack the bars. We can set them side-by-side setting the position argument to “dodge”.

The large number of Average homes makes it difficult to see the counts for the other categories. We could use coord_cartesian() to zoom in on the y-axis. An alternative approach is to view the proportion of homes with and without central air within each condition. We can do this by setting the position argument to “fill”.

The y-axis indicates proportions instead of counts.

Code along 4

Given a home’s condition, is it more likely to be in a particular high school district? For example, given all the homes in Excellent condition, is there a higher proportion of them in one of the high school districts?

Create a bar plot of Condition (Condition on x-axis), filled by HSDistrict. Set position = ‘fill’ so we visualize within each Condition the proportion belonging to a high school district.

Reminder: Insert a new code chunk by pressing Ctrl+Alt+I (Win/Linux) or Cmd+Option+I (Mac).

ggplot(homes) +
  aes(x = Condition, fill = HSDistrict) +
  geom_bar(position = "fill")

Numeric values vs groups

How does the TotalValue of homes differ between elementary school districts? One way to visualize this is with a boxplot. We can do this with geom_boxplot.

ggplot(homes) +
  aes(x = ESDistrict, y = TotalValue) +
  geom_boxplot()

The x-axis is hard to read. This happens sometimes. One solution is to simply rotate the plot. We can do that with the coord_flip() function. The coord_flip() function also allows us to zoom in on a plot with the xlim and ylim arguments. Let’s zoom in on the y-axis and look at homes less than 2 million in TotalValue.

ggplot(homes) +
  aes(x = ESDistrict, y = TotalValue) +
  geom_boxplot() +
  coord_flip(ylim = c(0,2e6))

Quick review of boxplots:

A similar plot is the violin plot, which allows us to compare the distributions between categories using smooth density plots. Use geom_violin()

ggplot(homes) +
  aes(x = ESDistrict, y = TotalValue) +
  geom_violin() +
  coord_flip(ylim = c(0,2e6))

Boxplots and violin plots obscure the total number within in each group. That’s not necessarily a bad thing. Too much information in a plot can be overwhelming. However we could create a 1-dimensional scatterplot, sometimes called a “stripchart”. The geom_jitter() function is like geom_point() except it lets you add some random noise to “jitter” the points.

Code along 5

How are the ages of homes distributed between the elementary school districts? Are there some elementary schools that serve mostly newer homes? Create a boxplot of Age of home by ESDistrict.

Reminder: Insert a new code chunk by pressing Ctrl+Alt+I (Win/Linux) or Cmd+Option+I (Mac).

ggplot(homes) +
  aes(x = ESDistrict, y = Age) +
  geom_boxplot() +
  coord_flip()

Line plots over time

How many homes have been built each year in Albemarle county? What year saw the most homes built? We can use the YearBuilt variable in our data to help answer this. We just need to count up the number of homes per YearBuilt and save the result as a data frame. We’ll use what we learned in the last session to do this.

homes_year <- homes %>%  
  count(YearBuilt) 
tail(homes_year, n = 10)

Let’s plot n vs YearBuilt using a line.

ggplot(homes_year) +
  aes(x = YearBuilt, y = n) +
  geom_line()

There was a boom sometime after 1750. Which year was it? Around 1950 it seems the number of homes built per year really started to take off. When was the peak? It also looks like we saw a dip sometime after 2000. What year was that?

The above plot shows some interesting trends and events, but it’s hard to get precise years and numbers. Fortunately there are ways to make ggplot graphs interactive. Here’s one way using the plotly package. plotly is package that allows you to make interactive web graphics. Simply call ggplotly() immediately after your ggplot code:

Load plotly…

# install.packages("plotly")
library(plotly)

Create plot as usual with ggplot2…

Call ggplotly() to create an interactive plot

Now we can interact with the plot to see information of interest. Hovering over points reveals precise information. We can also zoom in on portions of the plot. (Double-click in the plotting region to return to the full plot.)

We can also save our plot and call ggplotly() on it.

p <- ggplot(homes_year) +
  aes(x = YearBuilt, y = n) +
  geom_line()
ggplotly(p)

Code along 6

How has the mean number of FullBaths in a home changed over time (YearBuilt), since 1950?

The following code creates a data frame of mean FullBath by YearBuilt for 1950 to present, but only homes that have not been remodeled.

homes_mean_fb <- homes %>% 
  filter(YearBuilt >= 1950 & Remodeled == 0) %>% 
  group_by(YearBuilt) %>% 
  summarize(FullBath = mean(FullBath))
  
head(homes_mean_fb)

Create a line plot showing how the mean number of FullBaths has changed over time (YearBuilt). Try using ggplotly().

Reminder: Insert a new code chunk by pressing Ctrl+Alt+I (Win/Linux) or Cmd+Option+I (Mac).

ggplot(homes_mean_fb) +
  aes(x = YearBuilt, y = FullBath) +
  geom_line()

ggplotly()

A package that extends ggplot2: ggridges

There are many packages that extend ggplot, mainly by adding additional geoms or themes. One that is useful for our data is the ggridges package.

First notice the challenge of making comparisons of density histograms between 20 census tracts. (Census tracts are small subdivisions of a county that are used by the USA Census Bureau for statistical reporting.)

ggplot(homes) +
  aes(x = TotalValue) +
  geom_density() +
  facet_wrap(~CensusTract) +
  coord_cartesian(xlim = c(0, 1e6))

The ggridges package allows us to create ridgeline plots, which are density plots arranged in a staggered fashion. Notice the new geom geom_density_ridges().

# install.packages("ggridges")
library(ggridges)
ggplot(homes) +
  aes(x = TotalValue, y = CensusTract) + 
  geom_density_ridges() +
  coord_cartesian(xlim = c(0, 1e6))
Picking joint bandwidth of 41400

The ggridges package provides a custom theme specifically for use with ridgeline plots. Just tack on the function theme_ridges() to use it.

ggplot(homes) +
  aes(x = TotalValue, y = CensusTract) + 
  geom_density_ridges() +
  coord_cartesian(xlim = c(0, 1e6)) +
  theme_ridges()
Picking joint bandwidth of 41400

We can reorder the levels of CensusTract by a summary statistic such as the median of TotalValue. Notice we use the base R function reorder to accomplish this. That will produce a ridgeline plot where the distributions are ordered by median TotalValue of each census tract. Below we use the labs function to add a title and update the axis labels.

ggplot(homes) +
  aes(x = TotalValue, y = reorder(CensusTract, TotalValue, median)) + 
  geom_density_ridges() +
  coord_cartesian(xlim = c(0, 1e6)) +
  scale_x_continuous(labels = scales::dollar) +
  labs(y = "Census Tract", x = "Total Value", 
       title = "Distribution of Total Value by Census Tract") +
  theme_ridges()
Picking joint bandwidth of 41400

NOTE: ggplotly does not work with plots made by ggridges.

Creating interactive maps in R with leaflet

When we talk about real estate and census tracts, it’s natural to want to see visualizations that incorporate maps. Where is census tract 110 in Albemarle county? How big is it geographically?

To create maps we usually need latitude and longitude values so we can draw shapes, like the borders of counties, states and census tracts. Our data doesn’t have that information so we need to get it. Fortunately there is an R package that helps us quickly get this data. The tigris package allows us to download TIGER/Line shapefiles from the United States Census Bureau and load into R as “sf” objects. SF stands for “simple features” which is a standardized way of encoding spatial vector data in computers.

I already downloaded the census tract borders and saved as an rds file for us to use to help save time. The code I used is below, commented out. The final uncommented line reads in the data.

# install.packages("tigris")
# library(tigris)
# 
# this is going to download about 15 MB of data; may take a second
# alb <- tracts(state = "VA", county = "Albemarle", year = 2019)

# format the internal latitude and longitude values as numeric. These represent
# the geographic center of each census tract.

# alb <- alb %>%
#   mutate(INTPTLON = as.numeric(INTPTLON),
#          INTPTLAT = as.numeric(INTPTLAT)) %>% 
#   sf::st_transform(crs = '+proj=longlat +datum=WGS84')
# saveRDS(alb, file = "../data/alb.Rds")

alb <- readRDS(url("https://github.com/uvastatlab/DJA/raw/main/data/alb.Rds"))

Now that we have spatial data, we’re ready to create a map. For this we’ll use the leaflet package. The leaflet package takes a spatial data frame and allows you to layer on “Tiles” and “Polygons” (among much else). Tiles are base maps. The default is OpenStreetMap. Polygons are shapes drawn with the latitude and longitude coordinates stored in the geometry column of the alb data frame. In this case it draws the census tract boundaries. The code below produces an interactive map you can zoom in and move around.

library(leaflet)
leaflet(alb) %>% 
  addTiles() %>% 
  addPolygons() 

That’s nice but it would help if the census tracts were labeled. We can do that with addLabelOnlyMarkers(). We need to map the NAME column of the alb data frame to the label argument. Set lng = INTPTLON and lat = INTPTLAT to place labels at the geographic center of the census tracts. The leaflet package requires we place a tilde (~) in front of variable names to indicate they’re located in the data frame called in the leaflet function. Again the map that is rendered is interactive.

leaflet(alb) %>% 
  addTiles() %>% 
  addPolygons() %>% 
  addLabelOnlyMarkers(lng = ~INTPTLON, lat = ~INTPTLAT, label = ~NAME,
                      labelOptions = labelOptions(noHide = TRUE))
NA

If we like we can shade the census tract regions according to a summary statistic such as median total value of a home. Let’s do that! First we calculate median TotalValue by census tract, and then join with the alb data frame.

med_tv <- homes %>%
  group_by(CensusTract) %>% 
  summarize(TotalValue = median(TotalValue))

alb <- left_join(alb, med_tv, by = c("NAME" = "CensusTract"))

To shade the census tract regions we once again use addPolygons(). This time we assign the label argument to TotalValue to display the median home value when we hover over a census tract. We also add a legend using addLegend(). For both of these we use a color palette function that we create with the colorNumeric() function. That generates a color palette based on the numeric data we give it.

# create color palette for shading census tracts
pal <-  colorNumeric("YlOrRd", alb$TotalValue)
leaflet(alb_med_tv) %>% 
  addTiles() %>% 
  addPolygons() %>% 
  addLabelOnlyMarkers(lng = ~INTPTLON, lat = ~INTPTLAT, label = ~NAME,
                      labelOptions = labelOptions(noHide = TRUE)) %>% 
  addPolygons(stroke = FALSE, fillOpacity = 0.5, 
              smoothFactor = 0.5, 
              color = ~pal(TotalValue),
              label = ~TotalValue) %>% 
  addLegend(pal = pal, 
            values = ~TotalValue, 
            opacity = 0.9, 
            title = "Median Home Value", 
            position = "bottomleft")
NA

We’re done!

If you would like to talk more about ggplot2 or anything else statistics related, I would love to hear from you: clayford@virginia.edu or statlab@virginia.edu

References

LS0tDQp0aXRsZTogIkRhdGEgVmlzdWFsaXphdGlvbiBpbiBSIg0KYXV0aG9yOiAiQ2xheSBGb3JkLCBVVkEgTGlicmFyeSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KZWRpdG9yX29wdGlvbnM6IA0KICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lDQotLS0NCg0KDQojIyBRdWljayBJbnRybyB0byBSIE5vdGVib29rcyBhbmQgUiBNYXJrZG93bg0KDQpUaGlzIGlzIGFuIFIgTWFya2Rvd24gTm90ZWJvb2suIEl0IGNvbWJpbmVzIG1hcmtkb3duLCBhIHBsYWluIHRleHQgZm9ybWF0dGluZyBzeW50YXgsIGFuZCBSIGNvZGUuIFdoZW4geW91IGV4ZWN1dGUgUiBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHRoZSBvdXRwdXQgYXBwZWFycyBiZW5lYXRoIHRoZSBjb2RlLg0KDQpUaGlzIGZpbGUgd2FzIGNyZWF0ZWQgaW4gUlN0dWRpbyBieSBnb2luZyB0byBGaWxlLi4uTmV3IEZpbGUuLi5SIE5vdGVib29rLg0KDQpSIGNvZGUgbmVlZHMgdG8gYmUgaW4gImNodW5rcyIgZm9yIGl0IHRvIHdvcmsuIEJlbG93IGlzIGFuIGV4YW1wbGUgb2YgYW4gUiBjb2RlIGNodW5rLiBJdCBtYWtlcyBhIHBhcmFib2xhIHVzaW5nIGJhc2UgUiBncmFwaGljcyAobm90IGdncGxvdDIpLg0KDQpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ3RybCtTaGlmdCtFbnRlciogKFdpbi9MaW51eCkgb3IgKkNtZCtTaGlmdCtSZXR1cm4qIChNYWMpLg0KDQpgYGB7cn0NCnggPC0gc2VxKC0xLCAxLCBieSA9IDAuMDEpDQp5IDwtIHheMg0KcGxvdCh4LCB5LCB0eXBlID0gImwiKQ0KYGBgDQoNClRvIGhpZGUgdGhlIG91dHB1dCwgY2xpY2sgdGhlIEV4cGFuZC9Db2xsYXBzZSBvdXRwdXQgYnV0dG9uLiBUbyBjbGVhciByZXN1bHRzIChvciBhbiBlcnJvciksIGNsaWNrIHRoZSAieCIuDQoNCllvdSBjYW4gYWxzbyBwcmVzcyAqQ3RybCtFbnRlciogKFdpbi9MaW51eCkgb3IgKkNtZCtSZXR1cm4qIChNYWMpIHRvIHJ1biBvbmUgbGluZSBvZiBjb2RlIGF0IGEgdGltZSAoaW5zdGVhZCBvZiB0aGUgZW50aXJlIGNodW5rKS4NCg0KQWRkIGEgbmV3IFIgY29kZSBjaHVuayBieSBjbGlja2luZyB0aGUgKkluc2VydCBDaHVuayogYnV0dG9uIG9uIHRoZSB0b29sYmFyIG9yIGJ5IHByZXNzaW5nICpDdHJsK0FsdCtJKiAoV2luL0xpbnV4KSBvciAqQ21kK09wdGlvbitJKiAoTWFjKS4NCg0KIyMgQ29kZSBhbG9uZyAwDQoNCkluc2VydCBhIG5ldyBSIGNvZGUgY2h1bmsgYmVsb3cgYW5kIHR5cGUgYW5kIHJ1biB0aGUgY29kZTogMjIvNw0KDQoNCiMjIEltcG9ydCBkYXRhDQoNCkxldCdzIGltcG9ydCBvdXIgQWxiZW1hcmxlIENvdW50eSByZWFsIGVzdGF0ZSBkYXRhLiBXZSB1c2UgdGhlIGByZWFkUkRTKClgIGZ1bmN0aW9uLiBSZWNhbGwgdGhhdCBhbiBgcmRzYCBmaWxlIGlzIHNpbXBseSBhbnkgUiBvYmplY3QgdGhhdCdzIGJlZW4gc2F2ZWQuIEluIHRoaXMgY2FzZSB3ZSBzYXZlZCBhIGRhdGEgZnJhbWUuIEJlbG93IHdlIHJlYWQgaW4gdGhlIHJkcyBmaWxlIHZpYSBVUkwuIFRvIGRvIHRoaXMgd2UgbmVlZCB0byB1c2UgdGhlIGJhc2UgUiBgdXJsKClgIGZ1bmN0aW9uIHRvIG9wZW4gYSBjb25uZWN0aW9uIHRvIHRoZSB3ZWJzaXRlIHdoZXJlIHRoZSBmaWxlIGlzIHN0b3JlZC4NCg0KYGBge3J9DQpVUkwgPC0gImh0dHBzOi8vZ2l0aHViLmNvbS91dmFzdGF0bGFiL0RKQS9yYXcvbWFpbi9kYXRhL2FsYmVtYXJsZV9ob21lc18yMDIyLnJkcyINCmhvbWVzIDwtIHJlYWRSRFModXJsKFVSTCkpDQoNCmBgYA0KDQpMZXQncyBsb29rIGF0IHRoZSBmaXJzdCBmZXcgcm93czoNCg0KYGBge3J9DQpoZWFkKGhvbWVzKQ0KYGBgDQoNClZhcmlhYmxlIG5hbWUgZGVmaW5pdGlvbnM6DQoNCi0gICAqWWVhckJ1aWx0KjogeWVhciBob3VzZSB3YXMgYnVpbHQNCi0gICAqWWVhclJlbW9kZWxlZCo6IHllYXIgaG91c2Ugd2FzIHJlbW9kZWxlZA0KLSAgICpDb25kaXRpb24qOiBhc3Nlc3NlZCBjb25kaXRpb24gb2YgaG9tZQ0KLSAgICpGaW5TcUZ0KjogZmluaXNoZWQgc3F1YXJlIGZlZXQNCi0gICAqQ29vbGluZyo6IENlbnRyYWwgQWlyIGluZGljYXRvcg0KLSAgICpGUF9PcGVuKjogbnVtYmVyIG9mIGZpcmVwbGFjZXMNCi0gICAqQmVkcm9vbSo6IG51bWJlciBvZiBiZWRyb29tcw0KLSAgICpGdWxsQmF0aCo6IG51bWJlciBvZiBmdWxsIGJhdGhyb29tcyAodG9pbGV0LCBzaW5rIGFuZCBiYXRoKQ0KLSAgICpIYWxmQmF0aCo6IG51bWJlciBvZiBoYWxmIGJhdGhyb29tcyAodG9pbGV0IGFuZCBzaW5rIG9ubHkpDQotICAgKlRvdGFsUm9vbXMqOiB0b3RhbCByb29tcyBpbiBob3VzZQ0KLSAgICpMb3RTaXplKjogc2l6ZSBvZiBsYW5kIG9uIHdoaWNoIGhvbWUgaXMgbG9jYXRlZCwgaW4gYWNyZXMNCi0gICAqTGFuZFZhbHVlKjogYXNzZXNzZWQgdmFsdWUgb2YgdGhlIGxhbmQNCi0gICAqSW1wcm92ZW1lbnRzVmFsdWUqOiBhc3Nlc3NlZCB2YWx1ZSBvZiAiaW1wcm92ZW1lbnRzIiB0byBsb3QNCi0gICAqVG90YWxWYWx1ZSo6IHRvdGFsIGFzc2Vzc2VkIHZhbHVlIG9mIGhvbWUgYW5kIHByb3BlcnR5DQotICAgKkxhc3RTYWxlUHJpY2UqOiBwcmljZSBvZiBob21lIHdoZW4gbGFzdCBzb2xkDQotICAgKkxhc3RTYWxlRGF0ZSo6IGRhdGUgaG9tZSB3YXMgbGFzdCBzb2xkDQotICAgKkVTZGlzdHJpY3QqOiB0aGUgZWxlbWVudGFyeSBzY2hvb2wgdGhlIGhvbWUgZmVlZHMgaW50bw0KLSAgICpNU2Rpc3RyaWN0KjogdGhlIG1pZGRsZSBzY2hvb2wgdGhlIGhvbWUgZmVlZHMgaW50bw0KLSAgICpIU0Rpc3RyaWN0KjogdGhlIGhpZ2ggc2Nob29sIHRoZSBob21lIGZlZWRzIGludG8NCi0gICAqQ2Vuc3VzVHJhY3QqOiB0aGUgY2Vuc3VzIHRyYWN0IHRoZSBob21lIGlzIGxvY2F0ZWQgaW4NCi0gICAqQWdlKjogb2YgdGhlIGhvdXNlIGluIHllYXJzIGFzIG9mIDIwMjINCi0gICAqUmVtb2RlbGVkKjogaW5kaWNhdG9yIGlmIGhvdXNlIGhhcyBiZWVuIHJlbW9kZWxlZCAoMD1ubywgMT15ZXMpDQotICAgKkZQKjogaW5kaWNhdG9yIGlmIGhvdXNlIGhhcyBmaXJlcGxhY2UgKDA9bm8sIDE9eWVzKQ0KDQojIyBMb2FkIHBhY2thZ2VzIHdlJ2xsIHVzZSB0b2RheQ0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikgDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShzY2FsZXMpICMgZm9yIGZvcm1hdHRpbmcgYXhpcyB0aWNrIGxhYmVscw0KYGBgDQoNCg0KIyMgSW50cm8gdG8gZ2dwbG90Mg0KDQpUaGUgZ2dwbG90MiBwYWNrYWdlIGltcGxlbWVudHMgKlRoZSBHcmFtbWFyIG9mIEdyYXBoaWNzKiBhcyBkZWZpbmVkIGluIHRoZSBib29rIG9mIHRoZSBzYW1lIG5hbWUgYnkgTGVsYW5kIFdpbGtpbnNvbi4NCg0KSXQgcmVxdWlyZXMgeW91ciBkYXRhIGJlIGluIGEgZGF0YSBmcmFtZSAob3IgdGliYmxlKS4NCg0KTGV0J3MgZG8gYSBxdWljayBleGFtcGxlLiBIb3cgZG9lcyB0aGUgdG90YWwgdmFsdWUgb2YgYSBob21lIHJlbGF0ZSB0byBpdHMgc2l6ZSBpbiBmaW5pc2hlZCBzcXVhcmUgZmVldD8gUGxvdCBUb3RhbFZhbHVlIHZlcnN1cyBGaW5TcUZ0IHRvIGNyZWF0ZSBhIHNjYXR0ZXIgcGxvdC4NCg0KDQpgYGB7cn0NCmdncGxvdChob21lcykgKw0KICBhZXMoeCA9IEZpblNxRnQsIHkgPSBUb3RhbFZhbHVlKSArDQogIGdlb21fcG9pbnQoKSANCmBgYA0KDQpXaGF0IGp1c3QgaGFwcGVuZWQ/DQoNCi0gICBUaGUgYGdncGxvdGAgZnVuY3Rpb24gY3JlYXRlcyBhIHBsb3R0aW5nIGFyZWEgZm9yIGEgZGF0YSBmcmFtZQ0KLSAgIFRoZSBgYWVzYCBmdW5jdGlvbiBtYXBzIHZhcmlhYmxlcyBpbiB0aGUgZGF0YSBmcmFtZSB0byBhZXN0aGV0aWMgcHJvcGVydGllcyBvZiBnZW9tZXRyaWMgc2hhcGVzDQotICAgVGhlIGBnZW9tX3BvaW50YCBmdW5jdGlvbiBzcGVjaWZpZXMgd2Ugd2FudCB0byB1c2UgcG9pbnRzDQotICAgY29tYmluZSB0aGVzZSBmdW5jdGlvbnMgd2l0aCBgK2AsIHdoaWNoIGFkZHMgY29tcG9uZW50cyB0byBhIHBsb3QuIFRoZSBgK2AgaXMgbm90IGEgcGlwZSEgSXQgaXMgYW4gb3BlcmF0b3IuDQoNCkRvbid0IGZvcmdldCB0aGUgcGFyZW50aGVzZXMgb24gYGdlb21fcG9pbnQoKWAhDQoNCipNb3N0IG9mIHdoYXQgd2UgZG8gd2l0aCBnZ3Bsb3QyIHRha2VzIHRoaXMgZ2VuZXJhbCBmb3JtKg0KDQpgYGANCmdncGxvdChkYXRhKSArDQogIGFlcygpICsNCiAgZ2VvbV9zb21ldGhpbmcoKQ0KICANCmBgYA0KDQpPciB0aGlzIGZvcm0sIHdpdGggdGhlIGFlc3RoZXRpY3MgZGVmaW5lZCBpbiB0aGUgYGdncGxvdCgpYCBmdW5jdGlvbjoNCg0KYGBgDQpnZ3Bsb3QoZGF0YSwgYWVzKCkpICsNCiAgZ2VvbV9zb21ldGhpbmcoKQ0KDQpgYGANCg0KVGhlcmUgYXJlIG1hbnkgYGdlb21fYCBmdW5jdGlvbnMgaW4gZ2dwbG90Mi4gVGhpcyBuYW1pbmcgY29udmVudGlvbiBtYWtlcyB0aGVtIGVhc2llciB0byBmaW5kIHVzaW5nIFJTdHVkaW8ncyBhdXRvY29tcGxldGUgZnVuY3Rpb25hbGl0eS4gDQogICAgICANCkl0IGNhbiBnZXQgbXVjaCBtb3JlIGNvbXBsaWNhdGVkLCBidXQgZm9yIGV4cGxvcmF0b3J5IHZpc3VhbGl6YXRpb24gdGhpcyBvZnRlbiBzdWZmaWNpZW50Lg0KDQojIyBDb2RlIGFsb25nIDENCg0KRG8geW91bmdlciAobW9yZSByZWNlbnRseSBidWlsdCkgaG91c2VzIGhhdmUgYSB0ZW5kZW5jeSB0byBiZSBiaWdnZXIgdGhhbiBvbGRlciBob3VzZXM/IENyZWF0ZSBhIHNjYXR0ZXJwbG90IG9mIEZpblNxRnQgKHktYXhpcykgdmVyc3VzIEFnZSAoeC1heGlzKS4gDQoNCi0gVHJ5IGVudGVyaW5nIGBhbHBoYSA9IDEvMTBgIGluIGBnZW9tX3BvaW50KClgLiBXaGF0IGRvZXMgaXQgZG8/DQotIFRyeSBlbnRlcmluZyBgc2hhcGUgPSAiLiJgIGluIGBnZW9tX3BvaW50KClgLiBXaGF0IGRvZXMgaXQgZG8/DQotIFRyeSBlbnRlcmluZyBgc2hhcGUgPSAxYCBpbiBgZ2VvbV9wb2ludCgpYC4gV2hhdCBkb2VzIGl0IGRvPyAoc2VlIGA/cG9pbnRzYCBzaGFwZXMgYW5kIHRoZWlyIG51bWVyaWMgY29kZXMpDQoNClJlbWluZGVyOiBJbnNlcnQgYSBuZXcgY29kZSBjaHVuayBieSBwcmVzc2luZyAqQ3RybCtBbHQrSSogKFdpbi9MaW51eCkgb3IgKkNtZCtPcHRpb24rSSogKE1hYykuIA0KDQpgYGB7cn0NCmdncGxvdChob21lcykgKw0KICBhZXMoeCA9IEFnZSwgeSA9IEZpblNxRnQpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDEvMTApDQpgYGANCg0KDQoNCiMjIE1vcmUgYWVzdGhldGljcyBhbmQgZ2VvbXMsIGFuZCBpbnRyb2R1Y2luZyBmYWNldHMNCg0KUG9pbnRzIG5vdCBvbmx5IGhhdmUgcG9zaXRpb25zIGluIGEgcGxhbmUsIGJ1dCBjYW4gYmUgZGlmZmVyZW50IHNpemVzIGFuZCBoYXZlIGRpZmZlcmVudCBzaGFwZXMgYW5kIGNvbG9ycy4gV2UgY2FuICJtYXAiIHZhcmlhYmxlcyBpbiBvdXIgZGF0YSBmcmFtZSB0byB0aGVzZSBhZXN0aGV0aWNzLiBJbiBvdGhlciB3b3Jkcywgd2UgbGV0IHZhcmlhYmxlcyBkZWZpbmUgcHJvcGVydGllcyBvZiBwb2ludHMgc3VjaCBhcyBzaXplLCBzaGFwZSwgYW5kIGNvbG9yLg0KDQpIb3cgZG9lcyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gVG90YWxWYWx1ZSBhbmQgRmluU3FGdCBkaWZmZXIgYmV0d2VlbiBob21lcyB3aXRoIGFuZCB3aXRob3V0IGNlbnRyYWwgYWlyPw0KDQpQbG90IFRvdGFsVmFsdWUgdmVyc3VzIEZpblNxRnQgdG8gY3JlYXRlIGEgc2NhdHRlciBwbG90LCBidXQgYWxzbyBjb2xvciB0aGUgcG9pbnRzIGFjY29yZGluZyB0byB3aGV0aGVyIG9yIG5vdCB0aGUgaG9tZXMgaGF2ZSBjZW50cmFsIGFpciAoQ29vbGluZykgdXNpbmcgdGhlIGBjb2xvcmAgYXJndW1lbnQuIA0KDQpgYGB7cn0NCmdncGxvdChob21lcykgKw0KICBhZXMoeCA9IEZpblNxRnQsIHkgPSBUb3RhbFZhbHVlLCBjb2xvciA9IENvb2xpbmcpICsNCiAgZ2VvbV9wb2ludCgpIA0KYGBgDQoNCldlIGNhbiBhbHNvIG1hcCB0aGUgc2l6ZSBvZiB0aGUgcG9pbnRzIHRvIGEgdmFyaWFibGUsIHN1Y2ggYXMgTG90U2l6ZSwgdXNpbmcgdGhlIGBzaXplYCBhcmd1bWVudC4gTm90aWNlIEkgaGF2ZSBhbHNvIG1hcHBlZCB0aGUgc2hhcGUgb2YgdGhlIHBvaW50IHRvIENvb2xpbmcgdXNpbmcgdGhlIGBzaGFwZWAgYXJndW1lbnQuDQoNCmBgYHtyfQ0KZ2dwbG90KGhvbWVzKSArDQogIGFlcyh4ID0gRmluU3FGdCwgeSA9IFRvdGFsVmFsdWUsIA0KICAgICAgc2hhcGUgPSBDb29saW5nLCBzaXplID0gTG90U2l6ZSkgKw0KICBnZW9tX3BvaW50KCkgDQoNCmBgYA0KDQpOb3RpY2UgdGhlIG92ZXJwbG90dGluZy4gUGVyaGFwcyB3ZSBwcmVmZXIgdG8gc2VlIHNlcGFyYXRlIHNjYXR0ZXJwbG90cyBmb3IgZWFjaCBDb29saW5nIGNhdGVnb3J5LiBXZSBjYW4gYWNoaWV2ZSB0aGF0IHdpdGggKmZhY2V0cyouIFRoZSBmdW5jdGlvbiB0byB1c2UgaXMgYGZhY2V0X3dyYXAoKWAgd2l0aCB0aGUgdmFyaWFibGUgdGhhdCB3ZSB3YW50IHRvIGZhY2V0IG9uLiBJbiB0aGlzIGNhc2UgaXQncyBDb29saW5nLiBOb3RpY2Ugd2UgcHJlY2VkZSB0aGUgdmFyaWFibGUgd2l0aCBhIHRpbGRlIGB+YC4gV2UgY2FuIHJlYWQgdGhhdCBhcyAiZmFjZXQgYnkgQ29vbGluZyIuDQoNCmBgYHtyfQ0KZ2dwbG90KGhvbWVzKSArDQogIGFlcyh4ID0gRmluU3FGdCwgeSA9IFRvdGFsVmFsdWUsIHNpemUgPSBMb3RTaXplKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAxLzIpICsNCiAgZmFjZXRfd3JhcCh+IENvb2xpbmcpDQoNCmBgYA0KDQoNCldlIGNhbiBhbHNvIGhhdmUgbXVsdGlwbGUgZ2VvbXMgaW4gYSBwbG90LiBMZXQncyBzYXkgd2Ugd2FudCB0byBhZGQgYSBzbW9vdGggdHJlbmQgbGluZSB0aGF0IHN1bW1hcml6ZXMgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIEZpblNxRnQgYW5kIFRvdGFsVmFsdWUuIEFkZCB0aGUgYGdlb21fc21vb3RoKClgDQoNCmBgYHtyfQ0KZ2dwbG90KGhvbWVzKSArDQogIGFlcyh4ID0gRmluU3FGdCwgeSA9IFRvdGFsVmFsdWUpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDEvMikgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgZmFjZXRfd3JhcCh+IENvb2xpbmcpIA0KDQpgYGANCg0KIyMgQ29kZSBhbG9uZyAyDQoNCkhvdyBpcyBUb3RhbFZhbHVlIHJlbGF0ZWQgdG8gTG90U2l6ZSB3aXRoaW4gZWFjaCBoaWdoIHNjaG9vbCBkaXN0cmljdD8NCg0KQ3JlYXRlIGEgc2NhdHRlcnBsb3Qgb2YgVG90YWxWYWx1ZSAoeS1heGlzKSB2cyBMb3RTaXplICh4LWF4aXMpLCBmYWNldGVkIGJ5IEhTRGlzdHJpY3QuIEFkZCBzbW9vdGggdHJlbmQgbGluZXMgdG8gc3VtbWFyaXplIHRoZSByZWxhdGlvbnNoaXAuDQoNClJlbWluZGVyOiBJbnNlcnQgYSBuZXcgY29kZSBjaHVuayBieSBwcmVzc2luZyAqQ3RybCtBbHQrSSogKFdpbi9MaW51eCkgb3IgKkNtZCtPcHRpb24rSSogKE1hYykuIA0KDQpgYGB7cn0NCmdncGxvdChob21lcykgKw0KICBhZXMoeCA9IExvdFNpemUsIHkgPSBUb3RhbFZhbHVlKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBmYWNldF93cmFwKH5IU0Rpc3RyaWN0KQ0KYGBgDQoNCg0KDQojIyBCaW5uaW5nIGFuZCB6b29taW5nDQoNCkRlYWxpbmcgd2l0aCBsYXJnZSBhbW91bnRzIHRvIGRhdGEgbGVhZHMgdG8gb3ZlcnBsb3R0aW5nLiBTbyBmYXIgd2UndmUgdXNlZCBhbHBoYSB2YWx1ZXMgdG8gbW9kaWZ5IHRyYW5zcGFyZW5jeSBmcm9tIDAgKGludmlzaWJsZSkgdG8gMSAoc29saWQpLCBmYWNldHMsIGFuZCBkaWZmZXJlbnQgZ2VvbWV0cmljIHNoYXBlcyB0byBoZWxwIGFsbGV2aWF0ZSB0aGlzLiBBbm90aGVyIGlzICJiaW5uaW5nIi4gQXMgbG9uZyBhcyB5b3UgaGF2ZSB0aGUgYGhleGJpbmAgcGFja2FnZSBpbnN0YWxsZWQsIHRoaXMgY2FuIGJlIGVhc2lseSBhY2NvbXBsaXNoZWQgd2l0aCBgZ2VvbV9oZXhgLiBUaGlzICJiaW5zIiBjb3VudHMgb2YgcG9pbnRzIGludG8gaGV4YWdvbnMgYW5kIGNvbG9ycyB0aGUgaGV4YWdvbnMgYWNjb3JkaW5nIHRvIGNvdW50cy4gTGlnaHRlciBjb2xvcnMgbWVhbiBoaWdoZXIgY291bnRzLiBDaGFuZ2luZyB0aGUgbnVtYmVyIG9mIGJpbnMgKGRlZmF1bHQgMzAgeCAzMCkgZGVjcmVhc2VzL2luY3JlYXNlcyB0aGUgbnVtYmVyIG9mIGhleGFnb25zLiBGb3IgZXhhbXBsZSwgYGJpbnMgPSA0MGANCg0KYGBge3J9DQojIGluc3RhbGwucGFja2FnZXMoImhleGJpbiIpDQpnZ3Bsb3QoaG9tZXMpICsNCiAgYWVzKHggPSBGaW5TcUZ0LCB5ID0gVG90YWxWYWx1ZSkgKw0KICBnZW9tX2hleChiaW5zID0gNDApIA0KDQpgYGANCg0KDQpBbGJlbWFybGUgY291bnR5IGhhcyBob21lcyB3aXRoIHZlcnkgbGFyZ2UgVG90YWxWYWx1ZSBhbmQgTG90U2l6ZSB2YWx1ZXMuIEluY2x1ZGluZyB0aGVtIGluIG91ciBwbG90cyBtZWFucyB3ZSAiem9vbSBvdXQiIHRvIGFjY29tbW9kYXRlIHRoZW0gd2hpY2ggbWVhbnMgd2UncmUgImZhciBhd2F5IiBmcm9tIHRoZSBtYWpvcml0eSBvZiBob21lcy4NCg0KVG8gem9vbSBpbiBvbiBkYXRhIHdpdGggZ2dwbG90Miwgd2UgdXNlIHRoZSBgY29vcmRfY2FydGVzaW9uKClgIGZ1bmN0aW9uIGFuZCBkZWZpbmUgdGhlIGxpbWl0cyB0byB2aWV3IHdpdGggdGhlIGB4bGltYCBhbmQvb3IgYHlsaW1gIGFyZ3VtZW50cy4gDQoNCmBgYHtyfQ0KZ2dwbG90KGhvbWVzKSArDQogIGFlcyh4ID0gRmluU3FGdCwgeSA9IFRvdGFsVmFsdWUpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgoKSArIA0KICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoMCwgNTAwMCksICMgZnJvbSAwIHRvIDUsMDAwDQogICAgICAgICAgICAgICAgICB5bGltID0gYygwLCAyZTYpKSAjIGZyb20gMCB0byAkMiwwMDAsMDAwDQoNCmBgYA0KDQpJZiBwcmVzZW50aW5nIHRoaXMgcGxvdCB0byBhbiBhdWRpZW5jZSwgdGVsbCB0aGVtIGl0J3Mgem9vbWVkIGluIGFuZCBsb29raW5nIGF0IGEgc3Vic2V0IG9mIHlvdXIgZGF0YS4NCg0KVGhlIG51bWJlcnMgYXJlIGEgbGl0dGxlIGhhcmQgdG8gcmVhZCBvbiB0aGUgeS1heGlzLiBXZSBjYW4gZm9ybWF0IHRoZW0gYXMgZG9sbGFyIGFtb3VudHMgdXNpbmcgdGhlIGxhYmVscyBhcmd1bWVudCBpbiB0aGUgYHNjYWxlX3lfY29udGludW91cygpYCBmdW5jdGlvbi4gVG8gZG8gdGhpcyB3ZSBjYW4gdXNlIHRoZSBgZG9sbGFyYCBmdW5jdGlvbiBmcm9tIHRoZSBzY2FsZXMgcGFja2FnZSwgd2hpY2ggaXMgaW5zdGFsbGVkIHdoZW4geW91IGluc3RhbGwgdGhlIGdncGxvdDIgcGFja2FnZS4NCg0KYGBge3J9DQpnZ3Bsb3QoaG9tZXMpICsNCiAgYWVzKHggPSBGaW5TcUZ0LCB5ID0gVG90YWxWYWx1ZSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKDAsIDUwMDApLA0KICAgICAgICAgICAgICAgICAgeWxpbSA9IGMoMCwgMmU2KSkgKyANCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGRvbGxhcikgDQoNCmBgYA0KDQojIyBDb2RlIGFsb25nIDMNCg0KTW9kaWZ5IHlvdXIgY29kZSBmcm9tIGNvZGUgYWxvbmcgMiB0byB6b29tIGluIG9uIHRoZSB5LWF4aXMgdG8gc2VlIGhvbWUgcHJpY2VzIHJhbmdpbmcgZnJvbSAwIHRvIGAkMSwwMDAsMDAwYCwgYW5kIHpvb20gaW4gb24gdGhlIHgtYXhpcyB0byB2aWV3IGhvbWVzIG9uIGxvdHMgcmFuZ2luZyBpbiBzaXplIGZyb20gMCB0byA1IGFjcmVzLiBBbHNvIHVzZSB0aGUgZG9sbGFyIGZ1bmN0aW9uIHRvIGZvcm1hdCB0aGUgaG9tZSBwcmljZXMgb24gdGhlIHktYXhpcy4NCg0KYGBge3J9DQpnZ3Bsb3QoaG9tZXMpICsNCiAgYWVzKHggPSBMb3RTaXplLCB5ID0gVG90YWxWYWx1ZSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgZmFjZXRfd3JhcCh+IEhTRGlzdHJpY3QpICsNCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKDAsIDUpLA0KICAgICAgICAgICAgICAgICAgeWxpbSA9IGMoMCwgMWU2KSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gZG9sbGFyKQ0KDQpgYGANCg0KIyMgVmlzdWFsaXppbmcgY291bnRzDQoNClJlY2FsbCB0aGUgY29uZGl0aW9uIHZhcmlhYmxlLiBXZSBjYW4gcXVpY2tseSBnZXQgY291bnRzIG9mIGVhY2ggY29uZGl0aW9uIHVzaW5nIHRoZSBgY291bnQoKWAgZnVuY3Rpb24gZnJvbSB0aGUgZHBseXIgcGFja2FnZToNCg0KYGBge3J9DQpob21lcyAlPiUgY291bnQoQ29uZGl0aW9uKQ0KYGBgDQoNCk9uZSB3YXkgdG8gdmlzdWFsaXplIGNvdW50cyBpcyB3aXRoIGEgYmFyIHBsb3QuIFdlIGNhbiBjcmVhdGUgYSBiYXIgcGxvdCBpbiBnZ3Bsb3QyIHdpdGggYGdlb21fYmFyKClgLiBOb3RpY2UgaXQgYXV0b21hdGljYWxseSBnZW5lcmF0ZXMgdGhlIGNvdW50cyBmb3IgdXMuDQoNCmBgYHtyfQ0KZ2dwbG90KGhvbWVzKSArDQogIGFlcyh4ID0gQ29uZGl0aW9uKSArDQogIGdlb21fYmFyKCkNCmBgYA0KDQpPbmUgb2YgdGhlIGFlc3RoZXRpY3Mgb2YgYmFycyBpcyB0aGVpciAiZmlsbCIgY29sb3IuIFdlIGNhbiBtYXAgYSB2YXJpYWJsZSBmcm9tIG91ciBkYXRhIGZyYW1lIHRvIHRoZSBmaWxsIGFlc3RoZXRpYy4gRm9yIGV4YW1wbGUsIGxldCdzIHNlZSBjb3VudHMgb2YgY29uZGl0aW9ucyBmb3IgaG9tZXMgd2l0aCBhbmQgd2l0aG91dCBjZW50cmFsIGFpciAoQ29vbGluZykuDQoNCmBgYHtyfQ0KZ2dwbG90KGhvbWVzKSArDQogIGFlcyh4ID0gQ29uZGl0aW9uLCBmaWxsID0gQ29vbGluZykgKw0KICBnZW9tX2JhcigpDQoNCmBgYA0KDQpUaGUgZGVmYXVsdCByZXN1bHQgaXMgdG8gc3RhY2sgdGhlIGJhcnMuIFdlIGNhbiBzZXQgdGhlbSBzaWRlLWJ5LXNpZGUgc2V0dGluZyB0aGUgcG9zaXRpb24gYXJndW1lbnQgdG8gImRvZGdlIi4NCg0KYGBge3J9DQpnZ3Bsb3QoaG9tZXMpICsNCiAgYWVzKHggPSBDb25kaXRpb24sIGZpbGwgPSBDb29saW5nKSArDQogIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIikgDQoNCmBgYA0KDQpUaGUgbGFyZ2UgbnVtYmVyIG9mIEF2ZXJhZ2UgaG9tZXMgbWFrZXMgaXQgZGlmZmljdWx0IHRvIHNlZSB0aGUgY291bnRzIGZvciB0aGUgb3RoZXIgY2F0ZWdvcmllcy4gV2UgY291bGQgdXNlIGBjb29yZF9jYXJ0ZXNpYW4oKWAgdG8gem9vbSBpbiBvbiB0aGUgeS1heGlzLiBBbiBhbHRlcm5hdGl2ZSBhcHByb2FjaCBpcyB0byB2aWV3IHRoZSBwcm9wb3J0aW9uIG9mIGhvbWVzIHdpdGggYW5kIHdpdGhvdXQgY2VudHJhbCBhaXIgd2l0aGluIGVhY2ggY29uZGl0aW9uLiBXZSBjYW4gZG8gdGhpcyBieSBzZXR0aW5nIHRoZSBwb3NpdGlvbiBhcmd1bWVudCB0byAiZmlsbCIuDQoNCmBgYHtyfQ0KZ2dwbG90KGhvbWVzKSArDQogIGFlcyh4ID0gQ29uZGl0aW9uLCBmaWxsID0gQ29vbGluZykgKw0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKw0KICB5bGFiKCJwcm9wb3J0aW9uIikNCmBgYA0KDQpUaGUgeS1heGlzIGluZGljYXRlcyBwcm9wb3J0aW9ucyBpbnN0ZWFkIG9mIGNvdW50cy4NCg0KDQojIyBDb2RlIGFsb25nIDQNCg0KR2l2ZW4gYSBob21lJ3MgY29uZGl0aW9uLCBpcyBpdCBtb3JlIGxpa2VseSB0byBiZSBpbiBhIHBhcnRpY3VsYXIgaGlnaCBzY2hvb2wgZGlzdHJpY3Q/IEZvciBleGFtcGxlLCBnaXZlbiBhbGwgdGhlIGhvbWVzIGluIEV4Y2VsbGVudCBjb25kaXRpb24sIGlzIHRoZXJlIGEgaGlnaGVyIHByb3BvcnRpb24gb2YgdGhlbSBpbiBvbmUgb2YgdGhlIGhpZ2ggc2Nob29sIGRpc3RyaWN0cz8NCg0KQ3JlYXRlIGEgYmFyIHBsb3Qgb2YgQ29uZGl0aW9uIChDb25kaXRpb24gb24geC1heGlzKSwgZmlsbGVkIGJ5IEhTRGlzdHJpY3QuIFNldCBwb3NpdGlvbiA9ICdmaWxsJyBzbyB3ZSB2aXN1YWxpemUgd2l0aGluIGVhY2ggQ29uZGl0aW9uIHRoZSBwcm9wb3J0aW9uIGJlbG9uZ2luZyB0byBhIGhpZ2ggc2Nob29sIGRpc3RyaWN0Lg0KDQpSZW1pbmRlcjogSW5zZXJ0IGEgbmV3IGNvZGUgY2h1bmsgYnkgcHJlc3NpbmcgKkN0cmwrQWx0K0kqIChXaW4vTGludXgpIG9yICpDbWQrT3B0aW9uK0kqIChNYWMpLiANCg0KYGBge3J9DQpnZ3Bsb3QoaG9tZXMpICsNCiAgYWVzKHggPSBDb25kaXRpb24sIGZpbGwgPSBIU0Rpc3RyaWN0KSArDQogIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKQ0KYGBgDQoNCg0KIyMgTnVtZXJpYyB2YWx1ZXMgdnMgZ3JvdXBzDQoNCkhvdyBkb2VzIHRoZSBUb3RhbFZhbHVlIG9mIGhvbWVzIGRpZmZlciBiZXR3ZWVuIGVsZW1lbnRhcnkgc2Nob29sIGRpc3RyaWN0cz8gT25lIHdheSB0byB2aXN1YWxpemUgdGhpcyBpcyB3aXRoIGEgYm94cGxvdC4gV2UgY2FuIGRvIHRoaXMgd2l0aCBgZ2VvbV9ib3hwbG90YC4NCg0KYGBge3J9DQpnZ3Bsb3QoaG9tZXMpICsNCiAgYWVzKHggPSBFU0Rpc3RyaWN0LCB5ID0gVG90YWxWYWx1ZSkgKw0KICBnZW9tX2JveHBsb3QoKQ0KYGBgDQoNClRoZSB4LWF4aXMgaXMgaGFyZCB0byByZWFkLiBUaGlzIGhhcHBlbnMgc29tZXRpbWVzLiBPbmUgc29sdXRpb24gaXMgdG8gc2ltcGx5IHJvdGF0ZSB0aGUgcGxvdC4gV2UgY2FuIGRvIHRoYXQgd2l0aCB0aGUgYGNvb3JkX2ZsaXAoKWAgZnVuY3Rpb24uIFRoZSBgY29vcmRfZmxpcCgpYCBmdW5jdGlvbiBhbHNvIGFsbG93cyB1cyB0byB6b29tIGluIG9uIGEgcGxvdCB3aXRoIHRoZSB4bGltIGFuZCB5bGltIGFyZ3VtZW50cy4gTGV0J3Mgem9vbSBpbiBvbiB0aGUgeS1heGlzIGFuZCBsb29rIGF0IGhvbWVzIGxlc3MgdGhhbiAyIG1pbGxpb24gaW4gVG90YWxWYWx1ZS4NCg0KYGBge3J9DQpnZ3Bsb3QoaG9tZXMpICsNCiAgYWVzKHggPSBFU0Rpc3RyaWN0LCB5ID0gVG90YWxWYWx1ZSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGNvb3JkX2ZsaXAoeWxpbSA9IGMoMCwyZTYpKQ0KDQpgYGANCg0KUXVpY2sgcmV2aWV3IG9mIGJveHBsb3RzOg0KDQotICAgVGhlIHdpZHRoIG9mIHRoZSBib3ggKGFsb25nIHRoZSB5IGF4aXMpIHJlcHJlc2VudHMgdGhlIA0KICAgIG1pZGRsZSA1MCUgb2YgdGhlIGRhdGEuIEl0IGlzIGNhbGxlZCB0aGUgSW50ZXJxdWFydGlsZSBSYW5nZSAoSVFSKS4NCi0gICBUaGUgbGluZSBpbiB0aGUgYm94IGlzIHRoZSBtZWRpYW4uDQotICAgVGhlIHRvcCBvZiB0aGUgYm94IGlzIHRoZSA3NXRoIHBlcmNlbnRpbGUuDQotICAgVGhlIGJvdHRvbSBvZiB0aGUgYm94IGlzIHRoZSAyNXRoIHBlcmNlbnRpbGUuDQotICAgVGhlICJ3aGlza2VycyIgZXh0ZW5kaW5nIG91dCBvZiB0aGUgYm94IGFyZSB0byB0aGUgaGlnaGVzdCBhbmQgDQogICAgbG93ZXN0IHZhbHVlcyBub3QgdG8gZXhjZWVkIDEuNSB4IElRUg0KLSAgIFRoZSBkb3RzIGFyZSBjb25zaWRlcmVkICJvdXRsaWVycyIgcmVsYXRpdmUgdG8gdGhlIHJlc3Qgb2YgdGhlIGRhdGEuDQoNCkEgc2ltaWxhciBwbG90IGlzIHRoZSB2aW9saW4gcGxvdCwgd2hpY2ggYWxsb3dzIHVzIHRvIGNvbXBhcmUgdGhlIGRpc3RyaWJ1dGlvbnMgYmV0d2VlbiBjYXRlZ29yaWVzIHVzaW5nIHNtb290aCBkZW5zaXR5IHBsb3RzLiBVc2UgYGdlb21fdmlvbGluKClgDQoNCmBgYHtyfQ0KZ2dwbG90KGhvbWVzKSArDQogIGFlcyh4ID0gRVNEaXN0cmljdCwgeSA9IFRvdGFsVmFsdWUpICsNCiAgZ2VvbV92aW9saW4oKSArDQogIGNvb3JkX2ZsaXAoeWxpbSA9IGMoMCwyZTYpKQ0KYGBgDQoNCkJveHBsb3RzIGFuZCB2aW9saW4gcGxvdHMgb2JzY3VyZSB0aGUgdG90YWwgbnVtYmVyIHdpdGhpbiBpbiBlYWNoIGdyb3VwLiBUaGF0J3Mgbm90IG5lY2Vzc2FyaWx5IGEgYmFkIHRoaW5nLiBUb28gbXVjaCBpbmZvcm1hdGlvbiBpbiBhIHBsb3QgY2FuIGJlIG92ZXJ3aGVsbWluZy4gSG93ZXZlciB3ZSBjb3VsZCBjcmVhdGUgYSAxLWRpbWVuc2lvbmFsIHNjYXR0ZXJwbG90LCBzb21ldGltZXMgY2FsbGVkIGEgInN0cmlwY2hhcnQiLiBUaGUgYGdlb21faml0dGVyKClgIGZ1bmN0aW9uIGlzIGxpa2UgYGdlb21fcG9pbnQoKWAgZXhjZXB0IGl0IGxldHMgeW91IGFkZCBzb21lIHJhbmRvbSBub2lzZSB0byAiaml0dGVyIiB0aGUgcG9pbnRzLg0KDQpgYGB7cn0NCmdncGxvdChob21lcykgKw0KICBhZXMoeCA9IEVTRGlzdHJpY3QsIHkgPSBUb3RhbFZhbHVlKSArDQogIGdlb21faml0dGVyKHdpZHRoID0gMC4yLCBoZWlnaHQgPSAwLCBzaGFwZSA9ICIuIikgKw0KICBjb29yZF9mbGlwKHlsaW0gPSBjKDAsMmU2KSkNCg0KYGBgDQoNCiMjIENvZGUgYWxvbmcgNQ0KDQpIb3cgYXJlIHRoZSBhZ2VzIG9mIGhvbWVzIGRpc3RyaWJ1dGVkIGJldHdlZW4gdGhlIGVsZW1lbnRhcnkgc2Nob29sIGRpc3RyaWN0cz8gQXJlIHRoZXJlIHNvbWUgZWxlbWVudGFyeSBzY2hvb2xzIHRoYXQgc2VydmUgbW9zdGx5IG5ld2VyIGhvbWVzPyBDcmVhdGUgYSBib3hwbG90IG9mIEFnZSBvZiBob21lIGJ5IEVTRGlzdHJpY3QuDQoNClJlbWluZGVyOiBJbnNlcnQgYSBuZXcgY29kZSBjaHVuayBieSBwcmVzc2luZyAqQ3RybCtBbHQrSSogKFdpbi9MaW51eCkgb3IgKkNtZCtPcHRpb24rSSogKE1hYykuIA0KDQpgYGB7cn0NCmdncGxvdChob21lcykgKw0KICBhZXMoeCA9IEVTRGlzdHJpY3QsIHkgPSBBZ2UpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQoNCg0KDQojIyBMaW5lIHBsb3RzIG92ZXIgdGltZQ0KDQpIb3cgbWFueSBob21lcyBoYXZlIGJlZW4gYnVpbHQgZWFjaCB5ZWFyIGluIEFsYmVtYXJsZSBjb3VudHk/IFdoYXQgeWVhciBzYXcgdGhlIG1vc3QgaG9tZXMgYnVpbHQ/IFdlIGNhbiB1c2UgdGhlIFllYXJCdWlsdCB2YXJpYWJsZSBpbiBvdXIgZGF0YSB0byBoZWxwIGFuc3dlciB0aGlzLiBXZSBqdXN0IG5lZWQgdG8gY291bnQgdXAgdGhlIG51bWJlciBvZiBob21lcyBwZXIgWWVhckJ1aWx0IGFuZCBzYXZlIHRoZSByZXN1bHQgYXMgYSBkYXRhIGZyYW1lLiBXZSdsbCB1c2Ugd2hhdCB3ZSBsZWFybmVkIGluIHRoZSBsYXN0IHNlc3Npb24gdG8gZG8gdGhpcy4gDQoNCmBgYHtyfQ0KaG9tZXNfeWVhciA8LSBob21lcyAlPiUgIA0KICBjb3VudChZZWFyQnVpbHQpIA0KdGFpbChob21lc195ZWFyLCBuID0gMTApDQpgYGANCg0KTGV0J3MgcGxvdCBuIHZzIFllYXJCdWlsdCB1c2luZyBhIGxpbmUuDQoNCmBgYHtyfQ0KZ2dwbG90KGhvbWVzX3llYXIpICsNCiAgYWVzKHggPSBZZWFyQnVpbHQsIHkgPSBuKSArDQogIGdlb21fbGluZSgpDQpgYGANCg0KVGhlcmUgd2FzIGEgYm9vbSBzb21ldGltZSBhZnRlciAxNzUwLiBXaGljaCB5ZWFyIHdhcyBpdD8gQXJvdW5kIDE5NTAgaXQgc2VlbXMgdGhlIG51bWJlciBvZiBob21lcyBidWlsdCBwZXIgeWVhciByZWFsbHkgc3RhcnRlZCB0byB0YWtlIG9mZi4gV2hlbiB3YXMgdGhlIHBlYWs/IEl0IGFsc28gbG9va3MgbGlrZSB3ZSBzYXcgYSBkaXAgc29tZXRpbWUgYWZ0ZXIgMjAwMC4gV2hhdCB5ZWFyIHdhcyB0aGF0Pw0KDQpUaGUgYWJvdmUgcGxvdCBzaG93cyBzb21lIGludGVyZXN0aW5nIHRyZW5kcyBhbmQgZXZlbnRzLCBidXQgaXQncyBoYXJkIHRvIGdldCBwcmVjaXNlIHllYXJzIGFuZCBudW1iZXJzLiBGb3J0dW5hdGVseSB0aGVyZSBhcmUgd2F5cyB0byBtYWtlIGdncGxvdCBncmFwaHMgaW50ZXJhY3RpdmUuIEhlcmUncyBvbmUgd2F5IHVzaW5nIHRoZSBwbG90bHkgcGFja2FnZS4gcGxvdGx5IGlzIHBhY2thZ2UgdGhhdCBhbGxvd3MgeW91IHRvIG1ha2UgaW50ZXJhY3RpdmUgd2ViIGdyYXBoaWNzLiBTaW1wbHkgY2FsbCBgZ2dwbG90bHkoKWAgaW1tZWRpYXRlbHkgYWZ0ZXIgeW91ciBnZ3Bsb3QgY29kZToNCg0KTG9hZCBwbG90bHkuLi4NCg0KYGBge3J9DQojIGluc3RhbGwucGFja2FnZXMoInBsb3RseSIpDQpsaWJyYXJ5KHBsb3RseSkNCmBgYA0KDQpDcmVhdGUgcGxvdCBhcyB1c3VhbCB3aXRoIGdncGxvdDIuLi4NCg0KYGBge3J9DQpnZ3Bsb3QoaG9tZXNfeWVhcikgKw0KICBhZXMoeCA9IFllYXJCdWlsdCwgeSA9IG4pICsNCiAgZ2VvbV9saW5lKCkNCmBgYA0KDQpDYWxsIGBnZ3Bsb3RseSgpYCB0byBjcmVhdGUgYW4gaW50ZXJhY3RpdmUgcGxvdA0KDQpgYGB7cn0NCmdncGxvdGx5KCkNCmBgYA0KDQoNCk5vdyB3ZSBjYW4gaW50ZXJhY3Qgd2l0aCB0aGUgcGxvdCB0byBzZWUgaW5mb3JtYXRpb24gb2YgaW50ZXJlc3QuIEhvdmVyaW5nIG92ZXIgcG9pbnRzIHJldmVhbHMgcHJlY2lzZSBpbmZvcm1hdGlvbi4gV2UgY2FuIGFsc28gem9vbSBpbiBvbiBwb3J0aW9ucyBvZiB0aGUgcGxvdC4gKERvdWJsZS1jbGljayBpbiB0aGUgcGxvdHRpbmcgcmVnaW9uIHRvIHJldHVybiB0byB0aGUgZnVsbCBwbG90LikNCg0KV2UgY2FuIGFsc28gc2F2ZSBvdXIgcGxvdCBhbmQgY2FsbCBgZ2dwbG90bHkoKWAgb24gaXQuDQoNCmBgYHtyfQ0KcCA8LSBnZ3Bsb3QoaG9tZXNfeWVhcikgKw0KICBhZXMoeCA9IFllYXJCdWlsdCwgeSA9IG4pICsNCiAgZ2VvbV9saW5lKCkNCmdncGxvdGx5KHApDQoNCmBgYA0KDQoNCg0KIyMgQ29kZSBhbG9uZyA2DQoNCkhvdyBoYXMgdGhlIG1lYW4gbnVtYmVyIG9mIEZ1bGxCYXRocyBpbiBhIGhvbWUgY2hhbmdlZCBvdmVyIHRpbWUgKFllYXJCdWlsdCksIHNpbmNlIDE5NTA/IA0KDQpUaGUgZm9sbG93aW5nIGNvZGUgY3JlYXRlcyBhIGRhdGEgZnJhbWUgb2YgbWVhbiBGdWxsQmF0aCBieSBZZWFyQnVpbHQgZm9yIDE5NTAgdG8gcHJlc2VudCwgYnV0IG9ubHkgaG9tZXMgdGhhdCBoYXZlIG5vdCBiZWVuIHJlbW9kZWxlZC4NCg0KYGBge3J9DQpob21lc19tZWFuX2ZiIDwtIGhvbWVzICU+JSANCiAgZmlsdGVyKFllYXJCdWlsdCA+PSAxOTUwICYgUmVtb2RlbGVkID09IDApICU+JSANCiAgZ3JvdXBfYnkoWWVhckJ1aWx0KSAlPiUgDQogIHN1bW1hcml6ZShGdWxsQmF0aCA9IG1lYW4oRnVsbEJhdGgpKQ0KICANCmhlYWQoaG9tZXNfbWVhbl9mYikNCmBgYA0KDQpDcmVhdGUgYSBsaW5lIHBsb3Qgc2hvd2luZyBob3cgdGhlIG1lYW4gbnVtYmVyIG9mIEZ1bGxCYXRocyBoYXMgY2hhbmdlZCBvdmVyIHRpbWUgKFllYXJCdWlsdCkuIFRyeSB1c2luZyBgZ2dwbG90bHkoKWAuDQoNClJlbWluZGVyOiBJbnNlcnQgYSBuZXcgY29kZSBjaHVuayBieSBwcmVzc2luZyAqQ3RybCtBbHQrSSogKFdpbi9MaW51eCkgb3IgKkNtZCtPcHRpb24rSSogKE1hYykuIA0KDQpgYGB7cn0NCmdncGxvdChob21lc19tZWFuX2ZiKSArDQogIGFlcyh4ID0gWWVhckJ1aWx0LCB5ID0gRnVsbEJhdGgpICsNCiAgZ2VvbV9saW5lKCkNCmdncGxvdGx5KCkNCmBgYA0KDQoNCiMjIEEgcGFja2FnZSB0aGF0IGV4dGVuZHMgZ2dwbG90MjogZ2dyaWRnZXMNCg0KVGhlcmUgYXJlIG1hbnkgcGFja2FnZXMgdGhhdCBleHRlbmQgZ2dwbG90LCBtYWlubHkgYnkgYWRkaW5nIGFkZGl0aW9uYWwgZ2VvbXMgb3IgdGhlbWVzLiBPbmUgdGhhdCBpcyB1c2VmdWwgZm9yIG91ciBkYXRhIGlzIHRoZSBnZ3JpZGdlcyBwYWNrYWdlLg0KDQpGaXJzdCBub3RpY2UgdGhlIGNoYWxsZW5nZSBvZiBtYWtpbmcgY29tcGFyaXNvbnMgb2YgZGVuc2l0eSBoaXN0b2dyYW1zIGJldHdlZW4gMjAgY2Vuc3VzIHRyYWN0cy4gKENlbnN1cyB0cmFjdHMgYXJlIHNtYWxsIHN1YmRpdmlzaW9ucyBvZiBhIGNvdW50eSB0aGF0IGFyZSB1c2VkIGJ5IHRoZSBVU0EgQ2Vuc3VzIEJ1cmVhdSBmb3Igc3RhdGlzdGljYWwgcmVwb3J0aW5nLikNCg0KYGBge3J9DQpnZ3Bsb3QoaG9tZXMpICsNCiAgYWVzKHggPSBUb3RhbFZhbHVlKSArDQogIGdlb21fZGVuc2l0eSgpICsNCiAgZmFjZXRfd3JhcCh+Q2Vuc3VzVHJhY3QpICsNCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKDAsIDFlNikpDQoNCmBgYA0KDQpUaGUgZ2dyaWRnZXMgcGFja2FnZSBhbGxvd3MgdXMgdG8gY3JlYXRlIHJpZGdlbGluZSBwbG90cywgd2hpY2ggYXJlIGRlbnNpdHkgcGxvdHMgYXJyYW5nZWQgaW4gYSBzdGFnZ2VyZWQgZmFzaGlvbi4gTm90aWNlIHRoZSBuZXcgZ2VvbSBgZ2VvbV9kZW5zaXR5X3JpZGdlcygpYC4NCg0KYGBge3J9DQojIGluc3RhbGwucGFja2FnZXMoImdncmlkZ2VzIikNCmxpYnJhcnkoZ2dyaWRnZXMpDQpnZ3Bsb3QoaG9tZXMpICsNCiAgYWVzKHggPSBUb3RhbFZhbHVlLCB5ID0gQ2Vuc3VzVHJhY3QpICsgDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoKSArDQogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygwLCAxZTYpKQ0KDQpgYGANCg0KDQpUaGUgZ2dyaWRnZXMgcGFja2FnZSBwcm92aWRlcyBhIGN1c3RvbSB0aGVtZSBzcGVjaWZpY2FsbHkgZm9yIHVzZSB3aXRoIHJpZGdlbGluZSBwbG90cy4gSnVzdCB0YWNrIG9uIHRoZSBmdW5jdGlvbiBgdGhlbWVfcmlkZ2VzKClgIHRvIHVzZSBpdC4NCg0KYGBge3J9DQpnZ3Bsb3QoaG9tZXMpICsNCiAgYWVzKHggPSBUb3RhbFZhbHVlLCB5ID0gQ2Vuc3VzVHJhY3QpICsgDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoKSArDQogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygwLCAxZTYpKSArDQogIHRoZW1lX3JpZGdlcygpDQoNCmBgYA0KDQoNCldlIGNhbiByZW9yZGVyIHRoZSBsZXZlbHMgb2YgQ2Vuc3VzVHJhY3QgYnkgYSBzdW1tYXJ5IHN0YXRpc3RpYyBzdWNoIGFzIHRoZSBtZWRpYW4gb2YgVG90YWxWYWx1ZS4gTm90aWNlIHdlIHVzZSB0aGUgYmFzZSBSIGZ1bmN0aW9uIGByZW9yZGVyYCB0byBhY2NvbXBsaXNoIHRoaXMuIFRoYXQgd2lsbCBwcm9kdWNlIGEgcmlkZ2VsaW5lIHBsb3Qgd2hlcmUgdGhlIGRpc3RyaWJ1dGlvbnMgYXJlIG9yZGVyZWQgYnkgbWVkaWFuIFRvdGFsVmFsdWUgb2YgZWFjaCBjZW5zdXMgdHJhY3QuIEJlbG93IHdlIHVzZSB0aGUgYGxhYnNgIGZ1bmN0aW9uIHRvIGFkZCBhIHRpdGxlIGFuZCB1cGRhdGUgdGhlIGF4aXMgbGFiZWxzLg0KDQpgYGB7cn0NCmdncGxvdChob21lcykgKw0KICBhZXMoeCA9IFRvdGFsVmFsdWUsIHkgPSByZW9yZGVyKENlbnN1c1RyYWN0LCBUb3RhbFZhbHVlLCBtZWRpYW4pKSArIA0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzKCkgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoMCwgMWU2KSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpICsNCiAgbGFicyh5ID0gIkNlbnN1cyBUcmFjdCIsIHggPSAiVG90YWwgVmFsdWUiLCANCiAgICAgICB0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgVG90YWwgVmFsdWUgYnkgQ2Vuc3VzIFRyYWN0IikgKw0KICB0aGVtZV9yaWRnZXMoKQ0KDQpgYGANCg0KTk9URTogYGdncGxvdGx5YCBkb2VzIG5vdCB3b3JrIHdpdGggcGxvdHMgbWFkZSBieSBnZ3JpZGdlcy4NCg0KIyMgQ3JlYXRpbmcgaW50ZXJhY3RpdmUgbWFwcyBpbiBSIHdpdGggbGVhZmxldA0KDQpXaGVuIHdlIHRhbGsgYWJvdXQgcmVhbCBlc3RhdGUgYW5kIGNlbnN1cyB0cmFjdHMsIGl0J3MgbmF0dXJhbCB0byB3YW50IHRvIHNlZSB2aXN1YWxpemF0aW9ucyB0aGF0IGluY29ycG9yYXRlIG1hcHMuIFdoZXJlIGlzIGNlbnN1cyB0cmFjdCAxMTAgaW4gQWxiZW1hcmxlIGNvdW50eT8gSG93IGJpZyBpcyBpdCBnZW9ncmFwaGljYWxseT8NCg0KVG8gY3JlYXRlIG1hcHMgd2UgdXN1YWxseSBuZWVkIGxhdGl0dWRlIGFuZCBsb25naXR1ZGUgdmFsdWVzIHNvIHdlIGNhbiBkcmF3IHNoYXBlcywgbGlrZSB0aGUgYm9yZGVycyBvZiBjb3VudGllcywgc3RhdGVzIGFuZCBjZW5zdXMgdHJhY3RzLiBPdXIgZGF0YSBkb2Vzbid0IGhhdmUgdGhhdCBpbmZvcm1hdGlvbiBzbyB3ZSBuZWVkIHRvIGdldCBpdC4gRm9ydHVuYXRlbHkgdGhlcmUgaXMgYW4gUiBwYWNrYWdlIHRoYXQgaGVscHMgdXMgcXVpY2tseSBnZXQgdGhpcyBkYXRhLiBUaGUgdGlncmlzIHBhY2thZ2UgYWxsb3dzIHVzIHRvIGRvd25sb2FkIFRJR0VSL0xpbmUgc2hhcGVmaWxlcyBmcm9tIHRoZSBVbml0ZWQgU3RhdGVzIENlbnN1cyBCdXJlYXUgYW5kIGxvYWQgaW50byBSIGFzICJzZiIgb2JqZWN0cy4gU0Ygc3RhbmRzIGZvciAic2ltcGxlIGZlYXR1cmVzIiB3aGljaCBpcyBhIHN0YW5kYXJkaXplZCB3YXkgb2YgZW5jb2Rpbmcgc3BhdGlhbCB2ZWN0b3IgZGF0YSBpbiBjb21wdXRlcnMuDQoNCkkgYWxyZWFkeSBkb3dubG9hZGVkIHRoZSBjZW5zdXMgdHJhY3QgYm9yZGVycyBhbmQgc2F2ZWQgYXMgYW4gcmRzIGZpbGUgZm9yIHVzIHRvIHVzZSB0byBoZWxwIHNhdmUgdGltZS4gVGhlIGNvZGUgSSB1c2VkIGlzIGJlbG93LCBjb21tZW50ZWQgb3V0LiBUaGUgZmluYWwgdW5jb21tZW50ZWQgbGluZSByZWFkcyBpbiB0aGUgZGF0YS4NCg0KYGBge3J9DQojIGluc3RhbGwucGFja2FnZXMoInRpZ3JpcyIpDQojIGxpYnJhcnkodGlncmlzKQ0KIyANCiMgdGhpcyBpcyBnb2luZyB0byBkb3dubG9hZCBhYm91dCAxNSBNQiBvZiBkYXRhOyBtYXkgdGFrZSBhIHNlY29uZA0KIyBhbGIgPC0gdHJhY3RzKHN0YXRlID0gIlZBIiwgY291bnR5ID0gIkFsYmVtYXJsZSIsIHllYXIgPSAyMDE5KQ0KDQojIGZvcm1hdCB0aGUgaW50ZXJuYWwgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSB2YWx1ZXMgYXMgbnVtZXJpYy4gVGhlc2UgcmVwcmVzZW50DQojIHRoZSBnZW9ncmFwaGljIGNlbnRlciBvZiBlYWNoIGNlbnN1cyB0cmFjdC4NCg0KIyBhbGIgPC0gYWxiICU+JQ0KIyAgIG11dGF0ZShJTlRQVExPTiA9IGFzLm51bWVyaWMoSU5UUFRMT04pLA0KIyAgICAgICAgICBJTlRQVExBVCA9IGFzLm51bWVyaWMoSU5UUFRMQVQpKSAlPiUgDQojICAgc2Y6OnN0X3RyYW5zZm9ybShjcnMgPSAnK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQnKQ0KIyBzYXZlUkRTKGFsYiwgZmlsZSA9ICIuLi9kYXRhL2FsYi5SZHMiKQ0KDQphbGIgPC0gcmVhZFJEUyh1cmwoImh0dHBzOi8vZ2l0aHViLmNvbS91dmFzdGF0bGFiL0RKQS9yYXcvbWFpbi9kYXRhL2FsYi5SZHMiKSkNCg0KYGBgDQoNCk5vdyB0aGF0IHdlIGhhdmUgc3BhdGlhbCBkYXRhLCB3ZSdyZSByZWFkeSB0byBjcmVhdGUgYSBtYXAuIEZvciB0aGlzIHdlJ2xsIHVzZSB0aGUgbGVhZmxldCBwYWNrYWdlLiBUaGUgbGVhZmxldCBwYWNrYWdlIHRha2VzIGEgc3BhdGlhbCBkYXRhIGZyYW1lIGFuZCBhbGxvd3MgeW91IHRvIGxheWVyIG9uICJUaWxlcyIgYW5kICJQb2x5Z29ucyIgKGFtb25nIG11Y2ggZWxzZSkuIFRpbGVzIGFyZSBiYXNlIG1hcHMuIFRoZSBkZWZhdWx0IGlzIE9wZW5TdHJlZXRNYXAuIFBvbHlnb25zIGFyZSBzaGFwZXMgZHJhd24gd2l0aCB0aGUgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSBjb29yZGluYXRlcyBzdG9yZWQgaW4gdGhlIGBnZW9tZXRyeWAgY29sdW1uIG9mIHRoZSBgYWxiYCBkYXRhIGZyYW1lLiBJbiB0aGlzIGNhc2UgaXQgZHJhd3MgdGhlIGNlbnN1cyB0cmFjdCBib3VuZGFyaWVzLiBUaGUgY29kZSBiZWxvdyBwcm9kdWNlcyBhbiBpbnRlcmFjdGl2ZSBtYXAgeW91IGNhbiB6b29tIGluIGFuZCBtb3ZlIGFyb3VuZC4gDQoNCmBgYHtyfQ0KbGlicmFyeShsZWFmbGV0KQ0KbGVhZmxldChhbGIpICU+JSANCiAgYWRkVGlsZXMoKSAlPiUgDQogIGFkZFBvbHlnb25zKCkgDQpgYGANCg0KDQpUaGF0J3MgbmljZSBidXQgaXQgd291bGQgaGVscCBpZiB0aGUgY2Vuc3VzIHRyYWN0cyB3ZXJlIGxhYmVsZWQuIFdlIGNhbiBkbyB0aGF0IHdpdGggYGFkZExhYmVsT25seU1hcmtlcnMoKWAuIFdlIG5lZWQgdG8gbWFwIHRoZSBOQU1FIGNvbHVtbiBvZiB0aGUgYGFsYmAgZGF0YSBmcmFtZSB0byB0aGUgYGxhYmVsYCBhcmd1bWVudC4gU2V0IGBsbmcgPSBJTlRQVExPTmAgYW5kIGBsYXQgPSBJTlRQVExBVGAgdG8gcGxhY2UgbGFiZWxzIGF0IHRoZSBnZW9ncmFwaGljIGNlbnRlciBvZiB0aGUgY2Vuc3VzIHRyYWN0cy4gVGhlIGxlYWZsZXQgcGFja2FnZSByZXF1aXJlcyB3ZSBwbGFjZSBhIHRpbGRlIChgfmApIGluIGZyb250IG9mIHZhcmlhYmxlIG5hbWVzIHRvIGluZGljYXRlIHRoZXkncmUgbG9jYXRlZCBpbiB0aGUgZGF0YSBmcmFtZSBjYWxsZWQgaW4gdGhlIGBsZWFmbGV0YCBmdW5jdGlvbi4gQWdhaW4gdGhlIG1hcCB0aGF0IGlzIHJlbmRlcmVkIGlzIGludGVyYWN0aXZlLiANCg0KYGBge3J9DQpsZWFmbGV0KGFsYikgJT4lIA0KICBhZGRUaWxlcygpICU+JSANCiAgYWRkUG9seWdvbnMoKSAlPiUgDQogIGFkZExhYmVsT25seU1hcmtlcnMobG5nID0gfklOVFBUTE9OLCBsYXQgPSB+SU5UUFRMQVQsIGxhYmVsID0gfk5BTUUsDQogICAgICAgICAgICAgICAgICAgICAgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKG5vSGlkZSA9IFRSVUUpKQ0KDQpgYGANCg0KDQpJZiB3ZSBsaWtlIHdlIGNhbiBzaGFkZSB0aGUgY2Vuc3VzIHRyYWN0IHJlZ2lvbnMgYWNjb3JkaW5nIHRvIGEgc3VtbWFyeSBzdGF0aXN0aWMgc3VjaCBhcyBtZWRpYW4gdG90YWwgdmFsdWUgb2YgYSBob21lLiBMZXQncyBkbyB0aGF0ISBGaXJzdCB3ZSBjYWxjdWxhdGUgbWVkaWFuIFRvdGFsVmFsdWUgYnkgY2Vuc3VzIHRyYWN0LCBhbmQgdGhlbiBqb2luIHdpdGggdGhlIGBhbGJgIGRhdGEgZnJhbWUuDQoNCg0KYGBge3J9DQptZWRfdHYgPC0gaG9tZXMgJT4lDQogIGZpbHRlcighaXMubmEoQ2Vuc3VzVHJhY3QpKSAlPiUgDQogIGdyb3VwX2J5KENlbnN1c1RyYWN0KSAlPiUgDQogIHN1bW1hcml6ZShUb3RhbFZhbHVlID0gbWVkaWFuKFRvdGFsVmFsdWUpKQ0KDQphbGJfbWVkX3R2IDwtIGxlZnRfam9pbihhbGIsIG1lZF90diwgYnkgPSBjKCJOQU1FIiA9ICJDZW5zdXNUcmFjdCIpKSAlPiUgDQogIGZpbHRlcighaXMubmEoVG90YWxWYWx1ZSkpDQoNCmBgYA0KDQpUbyBzaGFkZSB0aGUgY2Vuc3VzIHRyYWN0IHJlZ2lvbnMgd2Ugb25jZSBhZ2FpbiB1c2UgYGFkZFBvbHlnb25zKClgLiBUaGlzIHRpbWUgd2UgYXNzaWduIHRoZSBgbGFiZWxgIGFyZ3VtZW50IHRvIFRvdGFsVmFsdWUgdG8gZGlzcGxheSB0aGUgbWVkaWFuIGhvbWUgdmFsdWUgd2hlbiB3ZSBob3ZlciBvdmVyIGEgY2Vuc3VzIHRyYWN0LiBXZSBhbHNvIGFkZCBhIGxlZ2VuZCB1c2luZyBgYWRkTGVnZW5kKClgLiBGb3IgYm90aCBvZiB0aGVzZSB3ZSB1c2UgYSBjb2xvciBwYWxldHRlIGZ1bmN0aW9uIHRoYXQgd2UgY3JlYXRlIHdpdGggdGhlIGBjb2xvck51bWVyaWMoKWAgZnVuY3Rpb24uIFRoYXQgZ2VuZXJhdGVzIGEgY29sb3IgcGFsZXR0ZSBiYXNlZCBvbiB0aGUgbnVtZXJpYyBkYXRhIHdlIGdpdmUgaXQuIA0KDQpgYGB7cn0NCiMgY3JlYXRlIGNvbG9yIHBhbGV0dGUgZm9yIHNoYWRpbmcgY2Vuc3VzIHRyYWN0cw0KcGFsIDwtICBjb2xvck51bWVyaWMoIllsT3JSZCIsIGFsYiRUb3RhbFZhbHVlKQ0KbGVhZmxldChhbGJfbWVkX3R2KSAlPiUgDQogIGFkZFRpbGVzKCkgJT4lIA0KICBhZGRQb2x5Z29ucygpICU+JSANCiAgYWRkTGFiZWxPbmx5TWFya2VycyhsbmcgPSB+SU5UUFRMT04sIGxhdCA9IH5JTlRQVExBVCwgbGFiZWwgPSB+TkFNRSwNCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMobm9IaWRlID0gVFJVRSkpICU+JSANCiAgYWRkUG9seWdvbnMoc3Ryb2tlID0gRkFMU0UsIGZpbGxPcGFjaXR5ID0gMC41LCANCiAgICAgICAgICAgICAgc21vb3RoRmFjdG9yID0gMC41LCANCiAgICAgICAgICAgICAgY29sb3IgPSB+cGFsKFRvdGFsVmFsdWUpLA0KICAgICAgICAgICAgICBsYWJlbCA9IH5Ub3RhbFZhbHVlKSAlPiUgDQogIGFkZExlZ2VuZChwYWwgPSBwYWwsIA0KICAgICAgICAgICAgdmFsdWVzID0gflRvdGFsVmFsdWUsIA0KICAgICAgICAgICAgb3BhY2l0eSA9IDAuOSwgDQogICAgICAgICAgICB0aXRsZSA9ICJNZWRpYW4gSG9tZSBWYWx1ZSIsIA0KICAgICAgICAgICAgcG9zaXRpb24gPSAiYm90dG9tbGVmdCIpDQoNCmBgYA0KDQoNCiMjIFdlJ3JlIGRvbmUhDQoNCklmIHlvdSB3b3VsZCBsaWtlIHRvIHRhbGsgbW9yZSBhYm91dCBnZ3Bsb3QyIG9yIGFueXRoaW5nIGVsc2Ugc3RhdGlzdGljcyByZWxhdGVkLCBJIHdvdWxkIGxvdmUgdG8gaGVhciBmcm9tIHlvdTogYGNsYXlmb3JkQHZpcmdpbmlhLmVkdWAgb3IgYHN0YXRsYWJAdmlyZ2luaWEuZWR1YA0KDQoNCiMjIFJlZmVyZW5jZXMNCg0KLSBXaWNraGFtLCBILiAoMjAxNiksICpnZ3Bsb3QyOiBFbGVnYW50IEdyYXBoaWNzIGZvciBEYXRhIEFuYWx5c2lzLCAybmQgZWQqLCBTcHJpbmdlci4gaHR0cHM6Ly9nZ3Bsb3QyLWJvb2sub3JnLyAob24tbGluZSB2ZXJzaW9uIG9mIHdvcmstaW4tcHJvZ3Jlc3MgM3JkIGVkaXRpb24pDQoNCi0gV2lja2hhbSwgSC4gYW5kIEdyb2xlbXVuZCBHLiAoMjAxNyksICpSIGZvciBEYXRhIFNjaWVuY2UqLiBPJ1JlaWxseS4gaHR0cDovL3I0ZHMuaGFkLmNvLm56Lw0KDQotICAgQ2hlbmcgSiwgS2FyYW1iZWxrYXIgQiwgWGllIFkgKDIwMjIpLiBfbGVhZmxldDogQ3JlYXRlIEludGVyYWN0aXZlIFdlYiBNYXBzIHdpdGggdGhlDQogIEphdmFTY3JpcHQgJ0xlYWZsZXQnIExpYnJhcnlfLiBSIHBhY2thZ2UgdmVyc2lvbiAyLjEuMSwNCiAgPGh0dHBzOi8vQ1JBTi5SLXByb2plY3Qub3JnL3BhY2thZ2U9bGVhZmxldD4uDQogIA0KLSAgIEMuIFNpZXZlcnQuIEludGVyYWN0aXZlIFdlYi1CYXNlZCBEYXRhIFZpc3VhbGl6YXRpb24gd2l0aCBSLCBwbG90bHksIGFuZCBzaGlueS4NCiAgQ2hhcG1hbiBhbmQgSGFsbC9DUkMgRmxvcmlkYSwgMjAyMC4gPGh0dHBzOi8vcGxvdGx5LXIuY29tLz4NCg0K